package com.adam.springsecurity;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;

import javax.servlet.Filter;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                // Spring Security should completely ignore URLs starting with /resources/
                .antMatchers("/css/**", "/js/**", "/favicon.ico", "/images/**");
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, MyTokenRepositoryImpl myTokenRepository,
                                                   ClientRegistrationRepository clientRegistrationRepository,
                                                   OAuth2AuthorizedClientService oAuth2AuthorizedClientService) throws Exception {
        http.authorizeHttpRequests()
                .antMatchers("/user/login", "/user/logout", "/user/login/**", "/error/**").permitAll()
                .antMatchers("/projects/**").access(new MyAuthorizationManager())
//                .antMatchers("/resources/**").permitAll()
                .anyRequest().authenticated();
        http.formLogin(form -> form
                .loginPage("/user/login")
                .usernameParameter("username")
                .passwordParameter("password")
//                .loginProcessingUrl("/user/login")
                .successHandler(new MyAuthenticationSuccessHandler())
//                .failureHandler(new MyAuthenticationFailureHandler())
                //默认为重定向到错误页面
                .failureUrl("/user/login?error")
        );
        //开启oauth2会自动注册两个Filter到SecurityFilterChain: OAuth2AuthorizationRequestRedirectFilter, OAuth2LoginAuthenticationFilter
        //在UsernamePasswordAuthenticationFilter之前
        http.oauth2Login(oauth2 -> oauth2
                .loginPage("/user/login")
                //                .loginProcessingUrl("/user/login")
                //用户信息端点，定义从OAuth2UserRequest到OAuth2User的获取过程
                //默认给予ROLE_USER和以SCOPE_开头的两个权限，可以通过自定义OAuth2UserService定制权限列表
                .userInfoEndpoint(userInfo -> userInfo
                        .userService(oAuth2UserService())
//                        .userAuthoritiesMapper(grantedAuthoritiesMapper)
                )
                //授权端点，定义页面中a.href跳转的连接的基础uri，处理Github登录请求，将请求重定向到github.com
                .authorizationEndpoint(authorization -> authorization
                        .baseUri("/user/login/oauth2/authorization")
                )
                //重定向端点，在Github配置的callback url的基础uri，处理Github回调请求，带上code请求access_token，然后带上token请求用户信息
                .redirectionEndpoint(redirection -> redirection
                        .baseUri("/user/login/oauth2/callback/*")
                )
        );

        http.logout(logout -> logout
                .logoutUrl("/user/logout")
                .addLogoutHandler(new MyLogoutHandler())
                .logoutSuccessUrl("/user/login?logout")
        );
        http.exceptionHandling()
                //authenticationEntryPoint没有多大作用，只能捕获到InsufficientAuthenticationException
//                .authenticationEntryPoint((request, response, authException) -> {
//                    System.out.println("进入authenticationEntryPoint");
//                    request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, authException);
//                    request.getRequestDispatcher("/user/login").forward(request, response);
//                })
                .accessDeniedPage("/error/403")
                //.accessDeniedHandler(new MyAccessDeniedHandler())
        ;
        http.csrf(Customizer.withDefaults());
        http.rememberMe(rememberMe -> rememberMe
                        .key("privateKey")
                        .tokenRepository(myTokenRepository)
                        .tokenValiditySeconds(60 * 60 * 24 * 7)
//                .rememberMeParameter("remember-me")
//                .rememberMeCookieName("remember-me")
        );
        MyOAuth2LoginAuthenticationFilter myOAuth2LoginAuthenticationFilter = new MyOAuth2LoginAuthenticationFilter(
                clientRegistrationRepository, oAuth2AuthorizedClientService,
                "/user/login/oauth2/callback/*");
        http.addFilterBefore(myOAuth2LoginAuthenticationFilter, OAuth2LoginAuthenticationFilter.class);
        SecurityFilterChain securityFilterChain = http.build();
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManagerBuilder.class).getObject();
        myOAuth2LoginAuthenticationFilter.setAuthenticationManager(authenticationManager);
        injectMyDefaultOAuth2AuthorizationRequestResolver(securityFilterChain, "/user/login/oauth2/authorization");
        return securityFilterChain;
    }

    private void injectMyDefaultOAuth2AuthorizationRequestResolver(SecurityFilterChain securityFilterChain, String authorizationBaseUrl) {
        List<Filter> filters = securityFilterChain.getFilters();
        for(Filter filter: filters) {
            if(filter instanceof OAuth2AuthorizationRequestRedirectFilter) {
                try {
                    DefaultOAuth2AuthorizationRequestResolver internalResolver = ReflectUtils.getPrivateField(filter, "authorizationRequestResolver", DefaultOAuth2AuthorizationRequestResolver.class);
                    MyDefaultOAuth2AuthorizationRequestResolver resolver = new MyDefaultOAuth2AuthorizationRequestResolver(internalResolver, authorizationBaseUrl);
                    ReflectUtils.setPrivateField(filter, "authorizationRequestResolver", resolver);
                } catch (NoSuchFieldException | IllegalAccessException | IllegalArgumentException e) {

                }
                break;
            }
        }
    }

    private DefaultOAuth2UserService oAuth2UserService() {
        MyOAuth2UserService oAuth2UserService = new MyOAuth2UserService();
        oAuth2UserService.setRequestEntityConverter(new MyOAuth2UserRequestEntityConverter());
        return oAuth2UserService;
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .build();
    }
}
